1 module lib.gitdl;
2 
3 import std.net.curl : download;
4 import std.stdio : writeln;
5 import std.format : format;
6 import std.file : tempDir, exists, remove, mkdirRecurse, write, readText;
7 import std.path : buildPath, dirName;
8 import std.stdio : stderr;
9 
10 import lib.fs : moveRecurse, rmdirRecurseForce;
11 import lib.unzip : unzip;
12 
13 private auto timesTried = 0;
14 
15 /**
16   Download a Git repository from GitHub
17 
18   Params:
19       user = The username
20       repo = The repository name
21       tag = The tag to download
22       dest = The destination path for the downloaded repository
23       stagingDir = The directory to download the zip file into and unzip it. By default a temp direcotry is used
24       retryTimes = The number of times to retrying downloading. Defaults to `5`.
25 */
26 void gitdl(const string user, const string repo, const string tag,
27     const string dest, const string stagingDir = tempDir(), uint retryTimes = 5)
28 {
29   const cacheFile = buildPath(dest, ".gitdl_cache.txt");
30   if (exists(dest) && exists(cacheFile))
31   {
32     const cache = readText(cacheFile);
33     if (cache == tag)
34     {
35       writeln(format!"%s/%s#%s already downloaded to %s"(user, repo, tag, dest));
36       return;
37     }
38   }
39   mkdirRecurse(stagingDir); // ensure that staging dir exits
40   const name = repo ~ "-" ~ (tag[0] == 'v' ? tag[1 .. $] : tag); // v is removed from the begining of the tag
41   const zipName = name ~ ".zip";
42   const zipPath = buildPath(stagingDir, zipName);
43   const unzipDir = buildPath(stagingDir, name);
44   try
45   {
46     // downloading
47     writeln(format!"Downloading %s/%s#%s"(user, repo, tag));
48     if (!(exists(zipPath))) // cache
49     {
50       download(format!"https://github.com/%s/%s/archive/refs/tags/%s.zip"(user,
51           repo, tag), zipPath);
52     }
53     // unzip
54     unzip(zipPath);
55     // copy to the destination
56     rmdirRecurseForce(dest);
57     mkdirRecurse(dest);
58     moveRecurse(unzipDir, dest);
59   }
60   catch (Exception e)
61   {
62     // start clean
63     remove(zipPath);
64     rmdirRecurseForce(unzipDir);
65     rmdirRecurseForce(dest);
66     if (timesTried > retryTimes)
67     {
68       throw e;
69     }
70     stderr.writeln("Download failed with: \n", e, "\n retrying...");
71     timesTried++;
72     return gitdl(user, repo, tag, dest, stagingDir, retryTimes);
73   }
74   // when successful reset the times tried
75   timesTried = 0;
76   // write the cache
77   write(cacheFile, tag);
78 }